﻿/**
 * @file trap.c
 * @date 23.5.7
 * @author yrz
 * @brief 中断实现
 * @version 0.1
 * @status Stable

**/
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
//#include "spinlock.h"
#include "proc.h"
#include "defs.h"

//struct spinlock tickslock;
uint ticks;

extern char trampoline[], uservec[], userret[];

// in kernelvec.S, calls kerneltrap().
void kernelvec();

extern int devintr();

void
trapinit(void)
{
    //initlock(&tickslock, "time");
}


/**
 * @brief 设置为在内核中接收异常和陷阱
 
*/
void
trapinithart(void)
{
    w_stvec((uint64)kernelvec);
}


/**
 * @brief 处理用户空间产生的中断、异常或系统调用

*/
void
usertrap(void)
{
    int which_dev = 0;

    if ((r_sstatus() & SSTATUS_SPP) != 0)      // 判断处于内核态
        panic("usertrap: not from user mode");


    // 发送中断或异常到kerneltrap()
    w_stvec((uint64)kernelvec);

    struct proc* p = myproc();

    
    // 保存程序计数器
    p->trapframe->epc = r_sepc();

    if (r_scause() == 8) {
        // 系统调用

        if (killed(p))
            exit(-1);

 
        // 返回到下一条指令
        p->trapframe->epc += 4;


        // 完成寄存器修改后，使能中断
        intr_on();

        syscall();
    }
    else if ((which_dev = devintr()) != 0) {
        // ok
    }
    else {
        printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
        printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
        setkilled(p);
    }

    if (killed(p))
        exit(-1);


    // 计时器中断则放弃CPU
    if (which_dev == 2)
        yield();

    usertrapret();
}


/**
 * @brief 返回用户空间

*/
void
usertrapret(void)
{
    struct proc* p = myproc();

 
    // 关中断
    intr_off();

    
    // 发送系统调用、中断、异常到uservec
    uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
    w_stvec(trampoline_uservec);


    // 设置陷阱帧
    p->trapframe->kernel_satp = r_satp();         // 内核页表
    p->trapframe->kernel_sp = p->kstack + PGSIZE; // 进程的内核栈
    p->trapframe->kernel_trap = (uint64)usertrap;
    p->trapframe->kernel_hartid = r_tp();         // hart id



    
        // 设置为用户态
    unsigned long x = r_sstatus();
    x &= ~SSTATUS_SPP; // 将SPP清0，转到用户态
    x |= SSTATUS_SPIE; // 使能中断
    w_sstatus(x);


    // 中断程序计数器保存用户的程序计数器
    w_sepc(p->trapframe->epc);


    // 用户页表地址
    uint64 satp = MAKE_SATP(p->pagetable);

    
    // 跳转到储存器顶部的userret，用以切换页表到用户页表、重载用户寄存器、切换到用户态
    uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
    ((void (*)(uint64))trampoline_userret)(satp);
}


/**
 * @brief 内核产生的中断或异常的处理

*/
void
kerneltrap()
{
    int which_dev = 0;
    uint64 sepc = r_sepc();
    uint64 sstatus = r_sstatus();
    uint64 scause = r_scause();

    if ((sstatus & SSTATUS_SPP) == 0)
        panic("kerneltrap: not from supervisor mode");
    if (intr_get() != 0)
        panic("kerneltrap: interrupts enabled");

    if ((which_dev = devintr()) == 0) {
        printf("scause %p\n", scause);
        printf("sepc=%p stval=%p\n", r_sepc(), r_stval());
        panic("kerneltrap");
    }

    // 计时器中断时放弃CPU
    if (which_dev == 2 && myproc() != 0 && myproc()->state == RUNNING)
        yield();


    // yield时可能产生陷阱，存储寄存器的值
    w_sepc(sepc);
    w_sstatus(sstatus);
}

/**
 * @brief 时钟中断

*/
void
clockintr()
{
    //acquire(&tickslock);
    ticks++;
    wakeup(&ticks);
    //release(&tickslock);
}


/**
 * @brief 软件中断、外部中断
 * @return 计时器中断返回2，外设返回1，无法识别返回0
*/
int
devintr()
{
    uint64 scause = r_scause();

    if ((scause & 0x8000000000000000L) &&
        (scause & 0xff) == 9) {
        

        
        // irq 指示哪个设备产生中断
        int irq = plic_claim();

        if (irq == UART0_IRQ) {
            uartintr();
        }
        else if (irq == VIRTIO0_IRQ) {
            virtio_disk_intr();
        }
        else if (irq) {
            printf("unexpected interrupt irq=%d\n", irq);
        }


        // PLIC允许每个设备在一次产生最多一个中断。
        // 告知PLIC该设备可以进行下一次中断了
        if (irq)
            plic_complete(irq);

        return 1;
    }
    else if (scause == 0x8000000000000001L) {

        // 机器模式计时器产生的软件中断
        if (cpuid() == 0) {
            clockintr();
        }


        // 告知软件中断，通过改变sip的比特位
        w_sip(r_sip() & ~2);

        return 2;
    }
    else {
        return 0;
    }
}

